// SPDX-License-Identifier: GPL-2.0

#include <linux/of_address.h>

#include "phy-nx-usb.h"
#include "phy-nx-usb-reg.h"
#include "phy-nx-usb-common.h"

enum {
	USB20_OTG_PHY = 0,
	USB20_HOST_PHY,
};

#define USB20_OTG_PHY_REG_BASE		0x02200400
#define USB20_HOST_PHY_REG_BASE		0x02300400

static int usbphy_get_id(struct lb_nx_usbphy *nx_usbphy)
{
	int ret;

	if (nx_usbphy->res->start == USB20_OTG_PHY_REG_BASE)
		ret = USB20_OTG_PHY;
	else if (nx_usbphy->res->start == USB20_HOST_PHY_REG_BASE)
		ret = USB20_HOST_PHY;
	else
		ret = -1;	/* invalid */

	return ret;
}

static int usbphy_suspend(void __iomem *reg_base)
{
	int ret = 0;

	ret = lb_nx_usbphy_suspend(reg_base);

	return ret;
}

static int usbphy_resume(void __iomem *reg_base)
{
	int ret = 0;

	ret = lb_nx_usbphy_resume(reg_base);

	return ret;
}

static int n7v3_usbphy_suspend(struct lb_nx_usbphy *nx_usbphy, int is_suspend)
{
	if (is_suspend)
		return usbphy_suspend(nx_usbphy->reg_base);	/* suspend */
	else
		return usbphy_resume(nx_usbphy->reg_base);	/* resume */
}

static int usbphy_init(struct lb_nx_usbphy *nx_usbphy)
{
	int			ret;
	char			efuse_val;
	int			usbphy_id;
	void __iomem		*reg_addr;
	reg_usb_usbctrl_t	usb_ctrl_reg;
	reg_usb_phycfg_t	usb_phycfg;

	/*
	 * note:
	 * For the following operations, please see USB_Program_Guide
	 */

	/*
	 * 1. config USBCTRL
	 */
	reg_addr = nx_usbphy->reg_base + USBCTRL;
	usb_ctrl_reg.val = READREG32(reg_addr);

	/* 1.1 n7v3 must be disabled the "dma check ahb write end" */
	usb_ctrl_reg.bits.dma_check_hwend = 0;

	WRITEREG32(reg_addr, usb_ctrl_reg.val);

	udelay(10);

	/*
	 * 2. config PHYTUNE
	 */
	/* nothing need to do! */

	/*
	 * 3. config PHYCFG
	 */
	reg_addr = nx_usbphy->reg_base + PHYCFG;
	usb_phycfg.val = READREG32(reg_addr);

	/* 3.1 phy res */
#define USB20_OTG_EFUSE_ID		0x11
#define USB20_HOST_EFUSE_ID		0x12

	usb_phycfg.bits.resusb20_en = 1;

	usbphy_id = usbphy_get_id(nx_usbphy);
	if (usbphy_id == USB20_OTG_PHY) {
		ret = lb_nx_usbphy_read_efuse_ref(USB20_OTG_EFUSE_ID,
						  &efuse_val);
		if (!ret)
			usb_phycfg.bits.resusb20_sel = efuse_val & 0x7F;
	} else if (usbphy_id == USB20_HOST_PHY) {
		ret = lb_nx_usbphy_read_efuse_ref(USB20_HOST_EFUSE_ID,
						  &efuse_val);
		if (!ret)
			usb_phycfg.bits.resusb20_sel = efuse_val & 0x7F;
	}

	/* 3.2 phy ldo */
	usb_phycfg.bits.ldo3318_lpn_ctrl = 0;

	WRITEREG32(reg_addr, usb_phycfg.val);

	return 0;
}

static void usbphy_check_suspend_by_node(const char *node_name)
{
	bool			is_enable;
	void __iomem		*reg_base;
	struct device_node	*phy_node;

	phy_node = of_find_node_by_name(NULL, node_name);
	if (phy_node) {
		is_enable = of_device_is_available(phy_node);
		if (!is_enable) {
			reg_base = of_iomap(phy_node, 0);
			usbphy_suspend(reg_base);
		}
	}
}

/* note: n7v3 sw patch1, please see n7v3 USB_Program_Guide */
static void n7v3_usbphy_sw_patch1(struct lb_nx_usbphy *nx_usbphy)
{
	int			usbphy_id;

	/*
	 * note:
	 * the name of phy must correspond to that of the dts
	 * e.g.:
	 *	usbphy_0: usbphy0@2200400
	 *	usbphy_1: usbphy1@2300400
	 */
#define USB20_OTG_PHY_NODE_NAME		"usbphy0"
#define USB20_HOST_PHY_NODE_NAME	"usbphy1"

	usbphy_id = usbphy_get_id(nx_usbphy);
	if (usbphy_id == USB20_OTG_PHY)
		usbphy_check_suspend_by_node(USB20_HOST_PHY_NODE_NAME);
	else if (usbphy_id == USB20_HOST_PHY)
		usbphy_check_suspend_by_node(USB20_OTG_PHY_NODE_NAME);
}

static int n7v3_usbphy_init(struct lb_nx_usbphy *nx_usbphy)
{
	n7v3_usbphy_sw_patch1(nx_usbphy);

	return usbphy_init(nx_usbphy);
}

struct lb_nx_usbphy_soc_ops n7v3_usbphy_ops = {
	.init		= n7v3_usbphy_init,
	.suspend	= n7v3_usbphy_suspend,
};

struct lb_nx_usbphy_soc_ops *lb_usbphy_get_n7v3_ops(void)
{
	return &n7v3_usbphy_ops;
}
